﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using FileManagerSystem.DbInfos;
using FileManagerSystem.Extend;
using FileManagerSystem.Helper;
using FileManagerSystem.Models;
using FileManagerSystem.Service;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Writers;

namespace FileManagerSystem.Controllers
{
    public class FileDownloadController : ApiBaseController
    {
        /// <summary>
        /// 文件Service对象
        /// </summary>
        [Autowired]
        private IFileService _fileService { get; set; }

        /// <summary>
        /// 映射文件Service对象
        /// </summary>
        [Autowired]
        private IReflectFileService _reflectfileService { get; set; }

        /// <summary>
        /// 目录Service对象
        /// </summary>
        [Autowired]
        private IDirectoryService _directoryService { get; set; }

        /// <summary>
        /// 下载指定的文件
        /// </summary>
        /// <param name="fileGuid"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> Single(string fileGuid)
        {
            if (!Guid.TryParse(fileGuid, out Guid id))
                return BadRequest($"传入的guid不合法：{fileGuid}");

            var reflectfileInfo = await _reflectfileService.FindOne(x => x.Id == id);
            if (reflectfileInfo == null)
                return BadRequest("未找到文件");

            var fileInfo = await _fileService.FindOne(x => x.Id == reflectfileInfo.PId);
            var fileStream = new FileStream(FileOperateHelper.GetFileRelativePath(fileInfo), FileMode.Open);
            return File(fileStream, fileInfo.MimeType, reflectfileInfo.Name + reflectfileInfo.Extension, true);

        }

        /// <summary>
        /// 下载打包的文件
        /// </summary>
        /// <param name="fileDetail">请求打包下载的参数</param>
        /// <returns></returns>
        [HttpPost]
        public async Task Archive([FromBody] FileArchiveDownloadDetail fileDetail)
        {
            if (fileDetail.DirGuids.Length == 0 && fileDetail.FileGuids.Length == 0)
                throw new Exception($"请检查参数是否正确提交！");
            if (fileDetail.DirGuids.Contains("string") || fileDetail.DirGuids.Contains("undefined") || fileDetail.FileGuids.Contains("string") || fileDetail.FileGuids.Contains("undefined"))
                throw new Exception($"请检查参数是否正确提交！");

            var tempGuid = Guid.NewGuid().ToString();
            var tempArchiveDir = Path.Combine("StaticFiles", "TempArchiceFiles", tempGuid);
            try
            {
                //1.(创建临时文件夹)
                //  生成随机目录（用于临时打包输出）
                FileOperateHelper.CreateFileDir(tempArchiveDir);


                //2.(处理DirGuids)
                //  加载并拷贝数据库文件夹文件（记得改名），并生成指定名称的文件夹，将对应文件放入对应文件夹中，
                //  如果文件有虚拟路径需要判断是否是子文件夹，是的话需要创建子文件夹，记得对每个文件进行改名
                //      拉取文件可能包含映射文件，需要特殊处理
                foreach (var topDirGuid in fileDetail.DirGuids)
                {
                    if (topDirGuid.IsNullOrWhiteSpace()) continue;
                    if (!Guid.TryParse(topDirGuid, out Guid id))
                        throw new Exception($"传入的guid不合法：{topDirGuid}");

                    var dirInf = await _directoryService.FindOne(x => x.Id == id);
                    if (dirInf == null)
                        throw new Exception($"找不到目录");

                    //在临时目录下创建指定顶级文件夹
                    var topDirPath = Path.Combine(tempArchiveDir, dirInf.AbstractDirPath);
                    if (FileOperateHelper.CreateFileDir(topDirPath))
                    {
                        var dirFiles = await GetAllFileInDir(topDirGuid);

                        /*
                         * 创建文件夹 + 移动文件 + 重命名文件
                         */
                        foreach (var dirFile in dirFiles)
                        {
                            //处理文件虚拟路径(文件在目录中，不包含文件名)
                            dirFile.AbstractDirPath = dirFile.AbstractDirPath.Trim().Replace("/", @"\").Replace(@"\\", @"\");//将正斜杠全部替换为反斜杠
                            var dirFileAbstractPath = dirFile.AbstractDirPath.Split(@"\");
                            var topDirPaths = topDirPath.Split(@"\");
                            if (topDirPaths[topDirPaths.Length - 2] == dirFileAbstractPath.First())//如果文件虚拟路径被顶级文件夹虚拟路径包含，则需要裁剪该部分
                            {
                                dirFile.AbstractDirPath = string.Join(@"\", dirFileAbstractPath.TakeLast(dirFileAbstractPath.Length - 1));
                            }
                            var fileAbstractDirPath = Path.Combine(topDirPath, dirFile.AbstractDirPath);//文件存储的虚拟路径(文件在目录中，不包含文件名)
                            if (FileOperateHelper.CreateFileDir(fileAbstractDirPath))
                            {
                                //移动文件到虚拟目录对应的真实目录中，同时改名（如果文件已存在则跳过）
                                var fileAbstractPath = Path.Combine(fileAbstractDirPath, dirFile.Name + dirFile.Extension);//虚拟目标文件路径（真实名称+格式后缀）
                                var fileRealPath = Path.Combine(dirFile.DirPath, dirFile.PId.ToString() + dirFile.Extension);//真实原文件路径（GUID名称+格式后缀）
                                FileOperateHelper.CopyFile(fileRealPath, fileAbstractPath);
                            }
                        }
                    }
                }


                //3.(处理FileGuids)
                //  加载文件集合GUID，
                //  如果文件有虚拟路径需要创建文件夹，记得对每个文件进行改名
                foreach (var fileGuid in fileDetail.FileGuids)
                {
                    if (fileGuid.IsNullOrWhiteSpace()) continue;
                    if (!Guid.TryParse(fileGuid, out Guid id))
                        throw new Exception($"传入的guid不合法：{fileGuid}");

                    var reflectfileInfo = await _reflectfileService.FindOne(x => x.Id == id);
                    if (reflectfileInfo == null) continue;//未找到的文件直接跳过

                    var fileInfo = await _fileService.FindOne(x => x.Id == reflectfileInfo.PId);
                    if (fileInfo == null) continue;//未找到的文件直接跳过

                    /*
                     * 创建文件夹 + 移动文件 + 重命名文件
                     */
                    if (reflectfileInfo.AbstractDirPath == null)
                        reflectfileInfo.AbstractDirPath = "";

                    reflectfileInfo.AbstractDirPath = reflectfileInfo.AbstractDirPath.Trim().Replace("/", @"\").Replace(@"\\", @"\");//将正斜杠全部替换为反斜杠
                    var fileAbstractDirPath = Path.Combine(tempArchiveDir, reflectfileInfo.AbstractDirPath);

                    if (FileOperateHelper.CreateFileDir(fileAbstractDirPath))
                    {
                        //移动文件到虚拟目录对应的真实目录中，同时改名（如果文件已存在则跳过）
                        var fileAbstractPath = Path.Combine(fileAbstractDirPath, reflectfileInfo.Name + reflectfileInfo.Extension);//虚拟目标文件路径（真实名称+格式后缀）
                        var fileRealPath = Path.Combine(fileInfo.DirPath, fileInfo.Id.ToString() + fileInfo.Extension);//真实原文件路径（GUID名称+格式后缀）
                        FileOperateHelper.CopyFile(fileRealPath, fileAbstractPath);
                    }
                }

                //4.(压缩导出文件流)
                //  最后进行压缩包流式导出
                var fileName = fileDetail.ArchiveName;
                if (fileName.IsNullOrWhiteSpace() || fileName == "string")
                {
                    fileName = tempGuid;
                }
                await ReadDirectoryToZipStreamAsync(HttpContext, fileName + ".zip", tempArchiveDir);

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                //5.导出完毕后，删除该临时文件夹(暂时可以不做，改为用任务清除，但是报错可以删除临时打包文件夹)
                FileOperateHelper.TryDeleteDirectory(tempArchiveDir, true);//异步删除，无需等待
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
            }
        }

        /// <summary>
        /// 获取所有在指定顶级文件夹下的文件（如果发生映射重复文件也带出来，尽可能带出文件）
        /// </summary>
        /// <param name="topDirGuid"></param>
        /// <returns></returns>
        private async Task<List<CompleteFileResult>> GetAllFileInDir(string topDirGuid)
        {
            var dirReflectFiles = await _reflectfileService.FindAll(x => x.AbstractTopDirGuid == topDirGuid);//映射文件
            var dirReflectFilesPIDs = dirReflectFiles.Select(x => x.PId);//映射文件对应的原文件GUID集合
            var sourceFiles = await _fileService.FindAll(x => dirReflectFilesPIDs.Contains(x.Id));//映射文件对应的原文件集合
            return CompleteFileHelper.CompletingFileInfo(dirReflectFiles, sourceFiles);
        }

        /// <summary>
        /// 读取目录中的文件直接成流，导出给Response的Body中
        /// </summary>
        /// <param name="httpContext">http上下文</param>
        /// <param name="fileName">导出的压缩包名称</param>
        /// <param name="folder">需要被流式压缩的文件夹</param>
        /// <returns></returns>
        private static async Task ReadDirectoryToZipStreamAsync(HttpContext httpContext, string fileName, string folder)
        {
            await Task.Run(() =>
            {
                fileName = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(fileName));//名字需要处理一下，否则会乱码
                httpContext.Response.Clear();
                httpContext.Response.StatusCode = StatusCodes.Status200OK;
                httpContext.Response.Headers.Add("content-disposition", "attachment;filename=" + fileName);
                httpContext.Response.ContentType = "application/octet-stream";
                using (var archive = ZipArchive.Create())
                {
                    archive.AddAllFromDirectory(folder);
                    archive.SaveTo(httpContext.Response.Body, new WriterOptions(CompressionType.None)
                    {
                        ArchiveEncoding = new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8)
                    });
                }
            });
        }

        /// <summary>
        /// 读取目录中的文件直接成流，导出给Response的Body中
        /// </summary>
        /// <param name="httpContext">http上下文</param>
        /// <param name="fileName">导出的压缩包名称</param>
        /// <param name="folder">需要被流式压缩的文件夹</param>
        /// <returns></returns>
        private async Task ReadDirectoryToZipStreamAsync1(HttpContext httpContext, string fileName, string folder)
        {
            await Task.Run(() =>
            {
                fileName = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(fileName));//名字需要处理一下，否则会乱码
                httpContext.Response.Clear();
                httpContext.Response.StatusCode = StatusCodes.Status200OK;
                httpContext.Response.Headers.Add("content-disposition", "attachment;filename=" + fileName);
                httpContext.Response.ContentType = "application/octet-stream";
                using (var archive = ZipArchive.Create())
                {
                    archive.AddAllFromDirectory(folder);
                    var memory = new MemoryStream();

                    archive.SaveTo(memory, new WriterOptions(CompressionType.None)
                    {
                        ArchiveEncoding = new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8)
                    });

                    var readBytesLeft = memory.Length;
                    memory.Position = 0;

                    int bufferSize = 1024;//
                    byte[] buffer;

                    using (httpContext.Response.Body)
                    {
                        httpContext.Response.ContentLength = readBytesLeft;
                        while (readBytesLeft > 0)
                        {
                            if (httpContext.RequestAborted.IsCancellationRequested)
                            {//如果客户端放弃链接，立马停止
                                break;
                            }
                            buffer = new byte[bufferSize];

                            int currentRead = memory.ReadAsync(buffer, 0, bufferSize).Result;
                            httpContext.Response.Body.Write(buffer, 0, currentRead);//发送读取的内容数据到客户端浏览器
                            httpContext.Response.Body.Flush();//注意每次Write后，要及时调用Flush方法，及时释放服务器内存空间
                            readBytesLeft -= currentRead;
                        }
                    }
                    //archive.SaveTo(httpContext.Response.Body, new WriterOptions(CompressionType.None)
                    //{
                    //    ArchiveEncoding = new ArchiveEncoding(Encoding.UTF8, Encoding.UTF8)
                    //});
                }
            });
        }
    }
}
